(ns packet.example.terminal
  (:require [packet.core :as p :refer [defpacket defslots]]
            [packet.utils :refer [in? hex-string->bytes]]
            [packet.types :refer [defsimpletype u hex ubcd lstr ipaddr]]
            [packet.example.types :refer [voltage current power energy
                                   YYMDhm mhDMY smhDMY smhD]]
            [packet.example.dlt645 :refer [dlt645 b0 b1 b2 b3 b32 b10]]))

(defsimpletype baud-rate [1]
  (fn [bs] (* (bit-and (first bs) 0xff) 300))
  (fn [v len] (list (quot v 300))))

(defsimpletype point [2]
  (fn [bs]
    (let [[l h] bs]
      (cond (= l 0xff) 0xffff
            (= l 0) 0
            :else
            (let [n (some #(when (bit-test l %) %) (range 8))]
              (+ (* (dec h) 8) n 1)))))
  (fn [v len]
    (cond (zero? v) (list 0 0)
          (= v 0xffff) (list 0xff 0xff)
          :else (let [h (inc (quot (dec v) 8))
                      l (bit-set 0 (rem (dec v) 8))]
                  (list l h)))))

(defslots com-params
  com-addr (hex 8 "通信地址")
  com-type (u "通道类型" :desc {2 "GPRS/CDMA" 3 "PSTN" 4 "Ethernet" 6 "RS232/485" 7 "CSD" 8 "Radio"}))

(defslots data
  (cond
    (<= 0xe0000200 di 0xe0000220)
    ("抄表通道抄表参数"
     base-time (mhDMY "抄表通道抄表基准时间")
     period-unit (u "定时抄表周期单位" :desc ["分" "时" "日" "月"])
     period (u "定时抄表周期"))

    (<= 0xe0000230 di 0xe0000250)
    ("抄表通道参数配置表"
     valid (u "有效性标志" :desc ["无效" "有效"])
     point-count (ubcd 2 "存储点数")
     meter-type (u "电表类型" :desc {1 "单相电子表" 2 "多功能表" 3 "其他类型" 255 "所有类型"})
     user-type (u "重点用户属性" :desc {0 "普通用户" 1 "重点用户" 255 "所有类型"})
     da-count (u "信息点标识组数")
     da (repeat da-count (point "信息点标识"))
     di-count (u "数据标识编码组数")
     di (repeat di-count (hex 4 "数据标识")))

    (<= 0xe0000301 di 0xe00003fe)
    ("普通任务参数表"
     valid (u "有效性标志")
     report-base-time (mhDMY "上报基准时间")
     report-period-unit (u "定时上报周期单位" :desc ["分" "时" "日" "月"])
     report-period (u "定时上报周期")
     data-mode (u "数据结构方式")
     measure-base-time (mhDMY "采样基准时间")
     measure-period-unit (u "定时采样周期单位" :desc ["分" "时" "日" "月"])
     measure-period (u "定时采样周期")
     multiplying (u "数据抽取倍率")
     times (u 2 "执行次数")
     da-count (u "信息点标识组数")
     da (repeat da-count "信息点标识数组" (point))
     di-count (u "数据标识编码组数")
     di (repeat di-count "数据标识数组" (hex 4)))

    (<= 0xe0000401 di 0xe00004fe)
    ("中继任务参数表"
     valid (u "有效性标志")
     report-base-time (mhDMY "上报基准时间")
     report-period-unit (u "定时上报周期单位" :desc ["分" "时" "日" "月"])
     report-period (u "定时上报周期")
     measure-base-time (mhDMY "采样基准时间")
     measure-period-unit (u "定时采样周期单位" :desc ["分" "时" "日" "月"])
     measure-period (u "定时采样周期")
     multiplying (u "数据抽取倍率")
     relay-mode (u "中继类型")
     times (u 2 "执行次数")
     port (u "端口号")
     baud (baud-rate)
     check-mode (u :desc ["无校验" "偶校验" "奇校验"])
     data-bits (ubcd "数据位")
     stop-bits (u "停止位" :desc ["1位" "1.5位" "2位"])
     time-out (ubcd "超时时间(秒)")
     relay-len (u "中继报文长度")
     meter (packet dlt645 :len relay-len "中继报文"))

    (and (zero? (b3 di)) (<= (b1 di) 0x3f) (zero? (b0 di)))
    (e (energy :desc #(str "当前"
                           (get ["组合有功" "正向有功" "反向有功" "无功1" "无功2"
                                 "第一象限无功" "第二象限无功" "第三象限无功" "第四象限无功"
                                 "正向视在" "反向视在"]
                                (b2 di))
                           (case (b1 di) 0 "总" (str "费率" (b1 di)))
                           "电能")))

    (<= 0x0201 (b32 di) 0x0204)
    (case (b32 di)
      0x0201 (u (voltage :desc #(str (["" "A" "B" "C"] (b1 di)) "相电压")))
      0x0202 (i (current :desc #(str (["" "A" "B" "C"] (b1 di)) "相电流")))
      0x0203 (p (power :desc #(str (["总" "A相" "B相" "C相"] (b1 di)) "有功功率")))
      0x0204 (q (power :desc #(str (["总" "A相" "B相" "C相"] (b1 di)) "无功功率"))))

    :else
    (case di
      0xe0000000 (confirm (u "确认" :desc ["全部肯定" "全部否定"]))
      
      0xe0000100 (com0 (slots com-params "主站通信参数"))
      0xe0000101 (com1 (slots com-params "备用主站1通信参数"))
      0xe0000102 (com2 (slots com-params "备用主站2通信参数"))
      0xe0000103 (proxy-server (hex 8 "网关或代理服务器地址"))
      0xe0000104 (apn (hex 16 "APN"))
      0xe0000105 (user-name (lstr 32 "登录用户名"))
      0xe0000106 (password (lstr 32 "登录口令"))
      0xe0000107 (heart-beat (ubcd "心跳周期(分)"))
      0xe0000108 (redial (ubcd "掉线重拨间隔(分)"))
      0xe0000109 (times (ubcd "掉线重拨次数"))
      0xe000010a (tm (u "传输层方式" :desc ["TCP" "UDP"]))
      0xe000010b (enc (u "编码压缩方式" :desc ["非压缩" "压缩BIN编码"]))
      0xe000010f (++ 0xe0000100 - 0xe000010b)

      0xe0000110 (net-mode (u "终端通信网络模式" :desc {1 "2G" 2 "3G" 4 "4G"}))

      0xe0000120 (zone-addr (ubcd 3 "地区码"))
      0xe0000121 (terminal-addr (hex 3 "终端地址"))
      0xe0000122 (mode (u "终端通信工作方式" :desc ["混合模式" "客户机模式" "服务器模式"]))
      0xe0000123 (port (u 2 "监听端口号"))
      0xe0000124 (ip (ipaddr "终端当前通道IP地址"))
      0xe0000125 (mask (hex 4 "终端当前通道子网掩码"))
      0xe0000126 (gateway (ipaddr "终端当前通道网关地址"))
      0xe0000127 (fetch-mode (u "有线网络IP地址获取方式"))
      0xe000012f (++ 0xe0000120 - 0xe000012f)
      
      0xe0000130 (terminal-time (smhDMY "终端时间"))
      0xe0000221 (config-count (ubcd "有效的抄表参数配置表总数"))
      0xe0000300 (task-count (ubcd "有效的普通任务总数"))
      0xe0000400 (relay-count (ubcd "有效的中继任务总数"))

      0xe0800000 (valid (u "测量点状态" :desc ["无效" "有效"]))
      0xe0800001 (point-type (u "测量点性质" :desc ["?" "485表" "模拟量" "脉冲量" "计算值" "交流采样"]))
      0xe0800002 (ma (ubcd 6 "测量点地址"))
      0xe0800003 (protocol (u "测量点通信规约" :desc ["DL/T645-1997规约" "DL/T645-2007规约" "广东97电表规约" "广东07电表规约" "威胜自定义规约"]))
      0xe0800004 (meter-type (u "电量表类型" :desc ["?" "单相电子表" "多功能表" "其它类型"]))
      0xe0800005 (coll (u "总分类型" :desc ["用户表" "总表"]))
      0xe0800006 (vip (u "重点用户属性" :desc ["普通用户" "重点用户"]))
      0xe0800007 (cutoff (u "拉闸功能" :desc ["不带拉闸" "带拉闸"]))
      0xe0800008 (fees (ubcd "最大费率数"))
      0xe0800009 (ga (hex 6 "测量点对应采集器地址"))
      0xe080000a (port (u "测量点端口号"))
      0xe080000b ("端口参数"
                  baud (baud-rate)
                  check-mode (u :desc ["无校验" "偶校验" "奇校验"])
                  datalen (u "数据位")
                  stop-bits (u :desc ["1位" "1.5位" "2位"]))
      0xe080000c (ta (ubcd 2 "TA变比"))
      0xe080000d (tv (ubcd 2 "TV变比"))
      0xe080000f (++ 0xe0800000 - 0xe080000d)

      0xe0800030 (bits 1
                       phase (0 2 "相别" :desc {1 "A相" 2 "B相" 4 "C相" 7 "三相"})
                       phase-cap (3 4 "相线特征" :desc ["支持相线识别"
                                                        "不支持相线识别"
                                                        "相线不确定"
                                                        "保留"]))
      
      0xe1800005 (da-count (ubcd 2 "有效的测量点总数"))
      0xe1800009 ("终端抄表状态信息"
                  (ubcd 2 "要抄电表总块数")
                  (ubcd 2 "抄表成功块数")
                  (smhDMY "抄表开始时间")
                  (smhDMY "抄表结束时间"))
      
      0x0000ff00 ("当前组合有功电能数据块"
                  fees-count (ubcd "费率数")
                  es (repeat fees-count (energy))))))

(defpacket terminal
  (skip-until 0x68 a b a b 0x68)
  cad-len (u 2 "数据长度")
  (= cad-len)
  0x68
  (bits 1
        dir (7 "传输方向位" :desc ["下行" "上行"])
        prm (6 "启动标志位" :desc ["来自从动站" "来自启动站"])
        (when (= dir 0)
          fcb (5 "帧计数位")
          fcv (4 "帧计数有效位" :desc ["无效" "有效"])
          :else
          acd (5 "请求访问位" :desc ["无数据" "有数据"]))
        (when (= prm 0) 
          fc (0 3 "功能码" :desc {0 "确认" 8 "用户数据" 9 "无所召唤的数据" 11 "链路状态"})
          :else
          fc (0 3 "功能码" :desc {1 "复位命令" 4 "用户数据" 9 "链路测试" 10 "请求1级数据" 11 "请求2级数据"})))

  a1 (ubcd 3 "地区码")
  a2 (hex 3 "终端地址")
  a3 (u "主站地址")
  afc (u "应用层功能码" :desc ["确认/否认" "复位" "链路接口检测" "?"
                                  "写参数" "?" "身份认证及密码协商" "?"
                                  "?" "?" "读参数" "?"
                                  "读当前数据" "读历史数据" "读事件记录" "文件传输"
                                  "中继转发" "?" "读任务数据" "读告警数据"
                                  "级联命令" "用户自定义数据"])

  (bits 1
        tpv (7 "带时标" :desc ["无时标" "有时标"])
        frame-type (5 6 "连续帧类型" :desc ["中间帧" "尾帧" "首帧" "单帧"])
        con (4 "要求确认" :desc ["无需确认" "要求确认"])
        frame-seq (0 3 "帧序号"))

  body (repeat
        (< (+ &0
              (if (or (= afc 6) (and (= dir 0) (= afc 4))) 16 0)
              (* tpv 5))
           (+ cad-len 6))
        da (point "信息点标识")
        di (hex 4 "数据标识")
        (cond
          (= afc 0)
          (confirm (u "确认" :desc ["确定" "否定"]))
          
          (and (= afc 4) (= dir 1))
          (ecode (u "错误码" :desc ["正确" "中继命令没有返回" "设置内容非法" "密码权限不足"
                                       "无此数据项" "命令时间失效" "目标地址不存在" "校验失败"]))

          (and (= dir 0) (in? afc 13 14 18 19))
          ("数据块信息"
           start-time (YYMDhm "起始时间")
           end-time (YYMDhm "结束时间")
           (when (in? afc 13 18)
             ddens (u "数据密度" :desc ["终端存储密度" "1分钟" "5分钟" "15分钟"
                                           "30分钟" "1小时" "1日" "1月" "结算日"])))
          
          (and (= dir 1) (in? afc 13 18))
          (data (repeat (< (+ &0
                              (if (or (= afc 6) (and (= dir 0) (= afc 4))) 16 0)
                              (* tpv 5))
                           (+ cad-len 6))
                        (slots data)
                        start-time (YYMDhm "起始时间"))) 

          (= afc 16)
          ("中继报文"
           relay_type (ubcd "中继类型" :desc {0 "普通中继" 1 "拉闸命令" 2 "允许合闸命令" 3 "保电投入" 4 "保电解除"})
           (when (= dir 0)
             port (u "端口号")
             baud (baud-rate "波特率")
             cs (u "校验方式")
             bit-len (ubcd "数据位")
             stop (u "停止位")
             timeout (ubcd "超时时间(秒)"))
           relay-len (u "中继报文长度")
           meter (packet dlt645 :len relay-len "中继报文"))
          
          (= dir 1)
          (slots data)))
  
  (when (or (= afc 6) (and (= dir 0) (= afc 4)))
    pw (hex 16 "消息认证码"))
  (when (= tpv 1)
    send_time (smhD "启动帧发送时标")
    allowed_delay (u "允许发送传输延时时间"))
  (checksum "校验和" :from dir)
  0x16)

